package com.exonline.commons.dao;
/**
 * @desc 利用C3P0连接池和Apache DBUtils封装的数据库持久化操作的工具类（包含事务处理）
 * @author hbrj
 */

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;


public class DaoImpl<T> implements IDao<T> {

    // 1.日志记录器
    private static final Logger logger = Logger.getLogger(DaoImpl.class.getName());

    // 2.数据源对象
    private static ComboPooledDataSource dataSource;
    // 3.查询执行器
    private static QueryRunner queryRunner;

    /**
     * 4.静态代码块：用于初始化数据源和查询执行器。
     */
    static {
        // 4.1初始化数据源
        dataSource = new ComboPooledDataSource();
        Properties properties = new Properties();
        try (InputStream input = DaoImpl.class.getClassLoader().getResourceAsStream("jdbc.properties")) {
            properties.load(input);
            dataSource.setJdbcUrl(properties.getProperty("jdbc.url")); // 设置 JDBC URL
            dataSource.setUser(properties.getProperty("jdbc.username")); // 设置用户名
            dataSource.setPassword(properties.getProperty("jdbc.password")); // 设置密码
            dataSource.setDriverClass(properties.getProperty("jdbc.driver")); // 设置驱动类

            // 4.2设置 C3P0 配置参数
            dataSource.setMinPoolSize(Integer.parseInt(properties.getProperty("c3p0.minPoolSize", "5"))); // 最小连接池大小
            dataSource.setMaxPoolSize(Integer.parseInt(properties.getProperty("c3p0.maxPoolSize", "20"))); // 最大连接池大小
            dataSource.setAcquireIncrement(Integer.parseInt(properties.getProperty("c3p0.acquireIncrement", "5"))); // 每次增加的连接数
            dataSource.setMaxIdleTime(Integer.parseInt(properties.getProperty("c3p0.maxIdleTime", "1800"))); // 最大空闲时间
            dataSource.setMaxStatements(Integer.parseInt(properties.getProperty("c3p0.maxStatements", "50"))); // 最大缓存语句数
            dataSource.setPreferredTestQuery(properties.getProperty("c3p0.preferredTestQuery", "SELECT 1")); // 测试查询语句
            dataSource.setTestConnectionOnCheckin(Boolean.parseBoolean(properties.getProperty("c3p0.testConnectionOnCheckin", "true"))); // 连接归还时测试
            dataSource.setTestConnectionOnCheckout(Boolean.parseBoolean(properties.getProperty("c3p0.testConnectionOnCheckout", "false"))); // 连接获取时测试
        } catch (IOException | PropertyVetoException e) {
            throw new RuntimeException("无法加载 jdbc.properties 文件", e); // 抛出运行时异常
        }

        // 5.初始化 QueryRunner
        queryRunner = new QueryRunner(dataSource);
    }

    /**
     * @des    1.获取数据库连接。
     * @return 数据库连接对象Connection
     * @throws SQLException 如果获取连接失败
     */
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection(); // 返回数据库连接
    }

    /**
     * @desc  2.关闭数据库连接。
     * @param connection 数据库连接
     * @throws SQLException 如果关闭连接失败
     */
    public static void closeConnection(Connection connection) throws SQLException {
        if (connection != null) {
            connection.close(); // 关闭连接
        }
    }

    /**
     * @desc  3.查询获取一条数据
     * @param sql SQL 语句
     * @param handler 结果集处理器:例如：new BeanHandler<>(Student.class)即查询Student表中的一条数据
     * @param params 参数：占位符参数，在执行查询一条数据时候则必须要写参数
     * @return
     * @throws SQLException
     */
    @Override
    public <T> T queryForObject(String sql, ResultSetHandler<T> handler, Object... params) throws SQLException {
        logger.log(Level.INFO, "正在执行查询: {0}", sql);  // 记录日志
        Connection conn = null;
        try {
            conn = getConnection(); // 获取连接
            return queryRunner.query(conn, sql, handler, params); // 执行查询
        } finally {
            closeConnection(conn); // 关闭连接
        }
    }


    /**
     * @desc  4.查询多条数据(查询多条数据+分页查询+条件查询)
     * @param baseSql 基础 SQL 语句
     * @param conditions 条件列表：例如："age>10  and name like ?"
     * @param pageNum
     * @param pageSize
     * @param handler 结果集处理器:例如：new BeanListHandler<>(Student.class):即将结果ResultSet转变为List<Student>
     * @param params 参数：占位符参数：如果不写，即不传参，本次执行是查询全部
     * @return
     * @param <T>
     * @throws SQLException
     */
    @Override
    public <T> List<T> queryWithPagingAndConditions(String baseSql, String conditions, int pageNum,int pageSize,ResultSetHandler<List<T>> handler, Object... params) throws SQLException {
        StringBuilder sqlBuilder = new StringBuilder(baseSql);
        //1.添加条件
        if (conditions != null && !conditions.trim().isEmpty()) {
            sqlBuilder.append(" WHERE ").append(conditions);
        }

        //2.添加分页
        if (pageNum > 0 && pageSize > 0) {
            sqlBuilder.append(" LIMIT ").append((pageNum - 1) * pageSize).append(", ").append(pageSize);
        }
        new MapListHandler();
        String finalSql = sqlBuilder.toString();


        logger.log(Level.INFO, "正在执行查询: {0}", finalSql);

        //3.执行查询
        Connection conn = null;
        try {
            conn = getConnection();
            return queryRunner.query(conn, finalSql, handler, params);
        } finally {
            closeConnection(conn);
        }
    }


    /**
     * @desc  5.执行查询总数统计。
     * @param sql   要执行的SQL查询语句
     * @param params 可变参数数组，用于替换SQL语句中的参数占位符
     * @return 查询结果的记录数如果查询结果为空，则返回0
     * @throws SQLException 如果数据库访问操作失败
     */
    @Override
    public int queryCount(String sql, Object... params) throws SQLException {
        logger.log(Level.INFO, "正在执行查询: {0}", sql);  // 记录日志
        Connection conn = null;
        try {
            conn = getConnection(); // 获取连接
            Long count= queryRunner.query(conn, sql, new ScalarHandler<>(), params);
            return count == null ? 0 : count.intValue();// 返回查询结果
        } finally {
            closeConnection(conn); // 关闭连接
        }
    }

    /**
     * @des          6.执行更新操作并管理事务。
     * @param sql    SQL 更新语句
     * @param params 更新参数
     * @return       受影响的行数
     * @throws SQLException 如果执行更新失败
     */
    @Override
    public int updateWithTransaction(String sql, Object... params) throws SQLException {
        Connection conn = null;
        try {
            conn = getConnection(); // 获取连接
            conn.setAutoCommit(false); // 关闭自动提交
            int rowsAffected = queryRunner.update(conn, sql, params); // 执行更新
            conn.commit(); // 提交事务
            logger.log(Level.INFO, "事务已提交: {0}, 受影响的行数: {1}", new Object[]{sql, rowsAffected}); // 记录日志
            return rowsAffected; // 返回受影响的行数
        } catch (Exception e) {
            if (conn != null) {
                conn.rollback(); // 回滚事务
                logger.log(Level.SEVERE, "事务已回滚: {0}, 错误信息: {1}", new Object[]{sql, e.getMessage()});  // 记录日志
            }
            System.out.println("事务提交失败，开启事务回滚");
            throw e; // 抛出异常
        } finally {
            if (conn != null) {
                conn.setAutoCommit(true); // 恢复自动提交
                closeConnection(conn); // 关闭连接
            }
        }
    }

    /**
     * @des          7.批量执行更新操作并管理事务。
     * @param sqls   SQL 更新语句数组
     * @param params 更新参数数组
     * @return       每个更新操作受影响的行数数组
     * @throws SQLException 如果执行批量更新失败
     */
    @Override
    public int[] batchUpdateWithTransaction(String[] sqls, Object[][] params) throws SQLException {
        Connection conn = null;
        try {
            conn = getConnection(); // 获取连接
            conn.setAutoCommit(false); // 关闭自动提交

            // 创建一个 List 来存储每个 SQL 语句的结果
            List<Integer> rowsAffectedList = new ArrayList<>();

            for (int i = 0; i < sqls.length; i++) {
                int rowsAffected = queryRunner.update(conn, sqls[i], params[i]); // 执行单个更新
                rowsAffectedList.add(rowsAffected);
            }

            int[] rowsAffected = rowsAffectedList.stream().mapToInt(Integer::intValue).toArray();

            conn.commit(); // 提交事务
            logger.log(Level.INFO, "批处理事务已提交"); // 记录日志
            return rowsAffected; // 返回受影响的行数数组
        } catch (Exception e) {
            if (conn != null) {
                conn.rollback(); // 回滚事务
                logger.log(Level.SEVERE, "批处理事务已回滚, 错误信息: {0}", e.getMessage());// 记录日志
            }
            System.out.println("事务提交失败，开启事务回滚");
            throw  e;
        } finally {
            if (conn != null) {
                conn.setAutoCommit(true); // 恢复自动提交
                closeConnection(conn); // 关闭连接
            }
        }
    }
}
